/*
 * jxDao Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxDao.dao;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.Transient;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.jxDao.annotation.DBColumn;
import org.jxDao.annotation.DBTable;
import org.jxDao.dao.exception.JXDaoException;
import org.jxDao.dao.filter.FilterUtil;
import org.jxDao.dao.filter.Filterable;
import org.jxDao.dao.filter.GroupBy;
import org.jxDao.dao.filter.InFilter;
import org.jxDao.dao.filter.NotInFilter;
import org.jxDao.dao.filter.OrderBy;
import org.jxDao.dao.i18n.I18N;
import org.jxUtils.ReflectionUtil;
import org.jxUtils.TypeUtil;
import org.jxUtils.check.CheckUtil;
import org.jxUtils.check.ParameterCheck;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxDao
 * @package org.jxDao.dao
 * @date 11/04/2014 - 12:23:22
 * @param <Bean>
 */
public class GenericDao<Bean extends Persistable> implements Dao<Bean> {
	
	private static final String DELETE = "DELETE FROM @TBL \n@CNDT;";
	
	private static final String DROP_TABLE = "DROP TABLE @TBL;";
	
	private static final String INSERT = "INSERT INTO @TBL (@COL) \nVALUES (@VLS);";
	
	private static final String SELECT = "SELECT @COL \nFROM @TBL \n@CNDT;";
	
	private static final String TRUNCATE = "TRUNCATE @TBL @KSKD;";
	
	private static final String UPDATE = "UPDATE @TBL \nSET @VLS \n@CNDT;";
	
	private boolean isCacheLocal = true;
	
	private Set<Persistable> BEAN_CACHE = new HashSet<>();
	
	private Map<OPERATIONS, String> SQLs = new HashMap<>();
	
	private MappedColumn columnsMethods = new MappedColumn();
	
	private DBManager manager = null;
	
	private Conn localConnection = null;
	
	private String schemaName = "";
	
	private String tableName = null;
	
	private Class<?> beanClass = null;
	
	private boolean abortOperation = false;
	
	/**
	 * @param manager
	 * @param beanClass
	 * @param beanCache
	 */
	private GenericDao(final DBManager manager, final Class<? extends Persistable> beanClass, final Set<Persistable> beanCache) {
	
		ParameterCheck.isNull(beanCache, "beanCache");
		
		isCacheLocal = false;
		
		initGenericDao(manager, beanClass);
		
		//
		if(BEAN_CACHE.isEmpty()) {
			
			BEAN_CACHE = beanCache;
			
		}else {
			
			BEAN_CACHE.addAll(beanCache);
		}
	}
	
	/**
	 * @param scopeConnection
	 */
	@SuppressWarnings("unchecked")
	protected GenericDao(final Conn scopeConnection) {
	
		ParameterCheck.isNull(scopeConnection, "scopeConnection");
		
		localConnection = scopeConnection;
		
		initGenericDao(scopeConnection.getManager(), (Class<? extends Persistable>)ReflectionUtil.getParameterClass(getClass()).get(0));
	}
	
	/**
	 * @param manager
	 */
	@SuppressWarnings("unchecked")
	protected GenericDao(final DBManager manager) {
	
		initGenericDao(manager, (Class<? extends Persistable>)ReflectionUtil.getParameterClass(getClass()).get(0));
	}
	
	/**
	 * @param manager
	 * @param beanClass
	 */
	public GenericDao(final DBManager manager, final Class<? extends Persistable> beanClass) {
	
		initGenericDao(manager, beanClass);
	}
	
	/**
	 * @param bean
	 * @param clazz
	 * @throws RuntimeException
	 */
	private static <Bean> void forechValidateBean(final Bean bean, final Class<?> clazz) throws RuntimeException {
	
		//
		if(TypeUtil.isBean(clazz)) {
			
			forechValidateBean(bean, clazz.getSuperclass());
			
			validateBean(bean, clazz);
		}
	}
	
	/**
	 * @param bean
	 * @param clazz
	 * @throws RuntimeException
	 */
	private static <Bean> void validateBean(final Bean bean, final Class<?> clazz) throws RuntimeException {
	
		final List<String> messages = new ArrayList<>();
		
		try {
			//
			for(final PropertyDescriptor propBean : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
				
				//
				if(propBean.getWriteMethod() != null && propBean.getReadMethod() != null) {
					
					// Se for uma Classe Embarcada
					if(TypeUtil.isBean(propBean.getPropertyType()) && !Persistable.class.isAssignableFrom(propBean.getPropertyType())) {
						
						//
						for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(propBean.getPropertyType()).getPropertyDescriptors()) {
							
							//
							if( !propEmb.getName().equals("class") && propEmb.getReadMethod() != null) {
								
								try {
									//
									validateValue(propBean.getReadMethod().invoke(bean), propEmb, messages);
									
								}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
									
									throw new RuntimeException(exception);
								}
							}
						}
						
						continue;
					}
					
					//
					validateValue(bean, propBean, messages);
				}
			}
			
		}catch(final IntrospectionException introspectionException) {
			
			throw new RuntimeException(introspectionException);
		}
		
		//
		if( !messages.isEmpty()) {
			
			throw new RuntimeException("\n\n" + I18N.DAO.invalidObjectToClass(bean.getClass()) + ":" + messages.toString().replace("[", "\n").replace(", ", "\n").replace("]", "\n"));
		}
	}
	
	/**
	 * @param bean
	 * @param propBean
	 * @param messages
	 * @throws RuntimeException
	 */
	private static void validateValue(final Object bean, final PropertyDescriptor propBean, final List<String> messages) throws RuntimeException {
	
		final DBColumn dbColumn = propBean.getReadMethod().getAnnotation(DBColumn.class);
		
		//
		if(dbColumn != null) {
			
			try {
				//
				Object value = null;
				
				//
				if(bean != null) {
					
					value = propBean.getReadMethod().invoke(bean);
				}
				
				//
				if(dbColumn.isNotNull()) {
					
					//
					if(CheckUtil.isNullOrEmpty(value)) {
						
						messages.add(I18N.DAO.attributeCannotBeNull(propBean.getDisplayName()));
						
					}else if(value instanceof String && CheckUtil.isAbsoluteEmpty((String)value)) {
						
						messages.add(I18N.DAO.attributeCannotBeNull(propBean.getDisplayName()));
					}
				}
				
				//
				if(dbColumn.size() > 0) {
					
					//
					if(value instanceof String) {
						
						final String strValue = (String)value;
						
						//
						if(CheckUtil.isNotNull(strValue) && strValue.length() > dbColumn.size()) {
							
							messages.add(I18N.DAO.attributeLengthGreaterThan(propBean.getDisplayName(), dbColumn.size()));
						}
						
					}else if(value instanceof Number) {
						
						final Number numValue = (Number)value;
						
						final String[] strSize = String.valueOf(dbColumn.size()).split("\\.");
						
						final int i = Integer.parseInt(strSize[0]);
						final int d = strSize.length == 2 ? Integer.parseInt(strSize[1]) : 0;
						
						final String[] strValue = String.valueOf(numValue).split("\\.");
						
						//
						if(strValue[0].length() > i || (strValue.length == 2 ? strValue[1].length() > d : false)) {
							
							messages.add(I18N.DAO.attributeLengthGreaterThan(propBean.getDisplayName(), "[" + i + ", " + d + "]"));
						}
					}
				}
				
			}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
				
				throw new RuntimeException(exception);
			}
		}
	}
	
	/**
	 * @param bean
	 * @throws RuntimeException
	 */
	public static final <Bean> void validateBean(final Bean bean) throws RuntimeException {
	
		ParameterCheck.isNull(bean, "bean");
		
		forechValidateBean(bean, bean.getClass());
	}
	
	/**
	 * @param sql
	 * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements that return nothing
	 * @throws JXDaoException
	 */
	private int executeUpdate(final String sql) throws JXDaoException {
	
		abortOperation = false;
		
		Conn conn = null;
		
		int result = 0;
		
		try {
			//
			conn = getConn(false);
			
			result = conn.executeUpdate(sql);
			
			// Se é uma transação local, efetiva as operações realizadas
			if(getLocalConnection() == null) {
				
				//
				if(isOperationWasAborted()) {
					
					System.out.println(I18N.DAO.operationAborted());
					
					try {
						//
						conn.rollback();
						
					}catch(final JXDaoException jxDaoException) {
						
						throw newJXDaoException(sql, jxDaoException);
					}
					
					return 0;
					
				}else {
					
					conn.commit();
				}
			}
			
			return result;
			
		}catch(final Throwable exception) {
			
			//
			if(conn != null) {
				
				try {
					//
					conn.rollback();
					
				}catch(final JXDaoException jxDaoException) {
					
					throw newJXDaoException(sql, jxDaoException);
				}
			}
			
			throw newJXDaoException(sql, exception);
		}
	}
	
	/**
	 * @param bean
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @return filled bean
	 * @throws JXDaoException
	 */
	@SuppressWarnings("unchecked")
	private <T extends Persistable> T fetchByPKAndClass(final T bean, final Class<?>... fetch) throws JXDaoException {
	
		// TODO: Otimizar a lógica de "fetch" para diminuir a quantidade de SELECTs executados na Base de Dados
		
		//
		if(CheckUtil.isNotNullAndNotEmpty(fetch)) {
			
			//
			for(final Class<?> fetchClass : fetch) {
				
				//
				if(CheckUtil.isNotNull(fetchClass) && (bean.getClass().equals(fetchClass))) {
					
					// Se o DAO ainda não está no CACHE, então ele é criado e adicionado
					if( !DBManager.getDaoCache().containsKey(bean.getClass())) {
						
						final GenericDao<T> genericDao = new GenericDao<T>(getManager(), bean.getClass(), BEAN_CACHE) {};
						
						return genericDao.getByPK(bean, fetch);
						
					}else {
						
						return ((Dao<T>)DBManager.getDaoCache().get(bean.getClass())).getByPK(bean, fetch);
					}
				}
			}
		}
		
		return bean;
	}
	
	/**
	 * @param resultSet
	 * @param bean
	 * @param isSaveAction
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @return the bean or null
	 * @throws JXDaoException
	 */
	private Bean fillBean(final ResultSet resultSet, final Bean bean, final boolean isSaveAction, final Class<?>... fetch) throws JXDaoException {
	
		try {
			//
			if(resultSet.next()) {
				
				try {
					//
					final ResultSetMetaData metaData = resultSet.getMetaData();
					
					// Para cada coluna
					for(int colIndex = 1; colIndex <= metaData.getColumnCount(); colIndex++) {
						
						String colName = metaData.getColumnName(colIndex);
						
						Object value = resultSet.getObject(colIndex);
						
						//
						if(value != null) {
							
							final MappedColumn colMethod = columnsMethods.getColumnMethod(colName, bean.getClass());
							
							// Se não for uma Classe Embarcada
							if(colMethod != null) {
								
								// Se não for uma Classe Persistível
								if( !Persistable.class.isAssignableFrom(colMethod.getMethodGet().getReturnType())) {
									
									// 
									if(colMethod.getTypeInJava().isEnum()) {
										
										// 
										for(Object e : colMethod.getTypeInJava().getEnumConstants()) {
											
											// 
											if(value.toString().equals(e.toString())) {
												
												value = e;
											}
										}
									}
									
									try {
										//
										colMethod.getMethodSet().invoke(bean, value);
										
									}catch(final IllegalArgumentException illegalArgumentException) {
										
										throw new JXDaoException(I18N.DAO.valueIsNotSupportedInField(value, value.getClass().getSimpleName(), colMethod.getFieldName(), colMethod.getMethodGet().getReturnType().getSimpleName()));
									}
									
								}else {
									
									// Verifica com qual coluna da outra tabela essa faz ligação
									for(final PropertyDescriptor propBean : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
										
										final Method methodGet = propBean.getReadMethod();
										
										//
										if(methodGet != null && methodGet.getName().equals(colMethod.getMethodGet().getName())) {
											
											colName = "id";
											
											final DBColumn dbColumn = methodGet.getAnnotation(DBColumn.class);
											
											// Por padrão a ligação é com a coluna ID mas, caso não seja a ligação deve ser indicada na Annotation
											if(dbColumn != null && CheckUtil.isNotNullAndNotAbsoluteEmpty(dbColumn.foreignKeyWith())) {
												
												colName = dbColumn.foreignKeyWith().trim();
											}
											
											break;
										}
									}
									
									final Class<?> colType = colMethod.getMethodGet().getReturnType();
									
									// Verifica se o Objeto já existe no Cache
									Persistable object = findObjectInCache(colType, colName, value);
									
									//
									if(object == null) {
										
										//
										object = (Persistable)colMethod.getMethodGet().invoke(bean);
										
										//
										if(object == null) {
											
											object = (Persistable)colType.newInstance();
										}
										
										//
										for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(colType).getPropertyDescriptors()) {
											
											final Method methodGetEmb = propEmb.getReadMethod();
											
											final DBColumn dbColumnEmb = methodGetEmb.getAnnotation(DBColumn.class);
											
											//
											if(dbColumnEmb != null && dbColumnEmb.name().equals(colName) || propEmb.getName().toLowerCase().equals(colName)) {
												
												try {
													//
													propEmb.getWriteMethod().invoke(object, value);
													
												}catch(final IllegalArgumentException illegalArgumentException) {
													
													throw new JXDaoException(I18N.DAO.valueIsNotSupportedInField(value, value.getClass().getSimpleName(), colType.getSimpleName() + "." + propEmb.getName(), methodGetEmb.getReturnType().getSimpleName()));
												}
												
												object = fetchByPKAndClass(object, fetch);
												
												BEAN_CACHE.add(object);
												
												break;
											}
										}
									}
									
									//
									colMethod.getMethodSet().invoke(bean, object);
								}
								
							}else {
								
								final MappedColumn colMethodEmb = columnsMethods.getColumnMethod(colName);
								
								//
								if(colMethodEmb != null) {
									
									//
									for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
										
										//
										if(propEmb.getName().equals(colMethodEmb.getBeanAttribute())) {
											
											// Obtém o Objeto Embarcado
											Object objectEmb = propEmb.getReadMethod().invoke(bean);
											
											//
											if(objectEmb == null) {
												
												//
												if(propEmb.getPropertyType().isMemberClass()) {
													
													final Constructor<?> constructor = propEmb.getPropertyType().getConstructor(new Class[]{bean.getClass()});
													
													objectEmb = constructor.newInstance(new Object[]{bean});
													
												}else {
													
													objectEmb = propEmb.getPropertyType().newInstance();
												}
											}
											
											// Se for uma Classe Persistível
											if(Persistable.class.isAssignableFrom(colMethodEmb.getMethodGet().getReturnType())) {
												
												// Cria um novo objeto da Classe Persistível
												Object object = colMethodEmb.getMethodGet().getReturnType().newInstance();
												
												// Se o tipo do valor for um Número e a coluna começar com "fk_"
												if(TypeUtil.isNumber(value.getClass()) && colName.startsWith("fk_")) {
													
													final Method methodSet = ReflectionUtil.getMethod(object, "setId");
													final Method methodGet = ReflectionUtil.getMethod(object, "getId");
													
													//
													if(methodSet != null) {
														
														try {
															//
															methodSet.invoke(object, value);
															
														}catch(final IllegalArgumentException illegalArgumentException) {
															
															throw new JXDaoException(I18N.DAO.valueIsNotSupportedInField(value, value.getClass().getSimpleName(), object.getClass().getSimpleName() + ".id", methodGet.getReturnType().getSimpleName()));
														}
														
														object = fetchByPKAndClass((Persistable)object, fetch);
														
														BEAN_CACHE.add((Persistable)object);
													}
												}
												
												value = object;
											}
											
											try {
												//
												colMethodEmb.getMethodSet().invoke(objectEmb, value);
												
											}catch(final IllegalArgumentException illegalArgumentException) {
												
												throw new JXDaoException(I18N.DAO.valueIsNotSupportedInField(value, value.getClass().getSimpleName(), objectEmb.getClass().getSimpleName() + "." + colMethodEmb.getFieldName(), colMethodEmb.getMethodGet().getReturnType().getSimpleName()));
											}
											
											try {
												//
												propEmb.getWriteMethod().invoke(bean, objectEmb);
												
											}catch(final IllegalArgumentException illegalArgumentException) {
												
												throw new JXDaoException(I18N.DAO.valueIsNotSupportedInField(objectEmb, objectEmb.getClass().getSimpleName(), bean.getClass().getSimpleName() + "." + propEmb.getName(), propEmb.getReadMethod().getReturnType().getSimpleName()));
											}
											
											break;
										}
									}
								}
							}
						}
					}
					
					return bean;
					
				}catch(final Throwable throwable) {
					
					clearCache();
					
					throw new RuntimeException(throwable);
				}
			}
			
		}catch(final SQLException sqlException) {
			
			throw newJXDaoException(null, sqlException);
		}
		
		return null;
	}
	
	/**
	 * @param resultSet
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @return the bean or null
	 * @throws JXDaoException
	 */
	@SuppressWarnings("unchecked")
	private Bean fillBean(final ResultSet resultSet, final Class<?>... fetch) throws JXDaoException {
	
		try {
			//
			return fillBean(resultSet, (Bean)getBeanClass().newInstance(), false, fetch);
			
		}catch(final Throwable throwable) {
			
			clearCache();
			
			throw new JXDaoException(throwable);
		}
	}
	
	/**
	 * @param resultSet
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @return the beans in {@link Collection}
	 * @throws JXDaoException
	 */
	private List<Bean> fillList(final ResultSet resultSet, final Class<?>... fetch) throws JXDaoException {
	
		abortOperation = false;
		
		//
		if(isCacheLocal) {
			
			clearCache();
		}
		
		final List<Bean> beans = new ArrayList<>();
		
		Bean bean = fillBean(resultSet, fetch);
		
		//
		while(bean != null) {
			
			//
			if(isOperationWasAborted()) {
				
				System.out.println(I18N.DAO.operationAborted());
				
				return new ArrayList<Bean>();
			}
			
			fillChildren(bean, fetch);
			
			beans.add(bean);
			
			bean = fillBean(resultSet, fetch);
		}
		
		//
		if(isCacheLocal) {
			
			clearCache();
		}
		
		return beans;
	}
	
	/**
	 * @param colType
	 * @param colName
	 * @param value
	 * @return objectCache or <b>null</b>
	 * @throws IntrospectionException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private Persistable findObjectInCache(final Class<?> colType, final String colName, final Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
	
		//
		for(final PropertyDescriptor prop : Introspector.getBeanInfo(colType).getPropertyDescriptors()) {
			
			final Method methodGet = prop.getReadMethod();
			
			final DBColumn dbColumn = methodGet.getAnnotation(DBColumn.class);
			
			//
			if(dbColumn != null && dbColumn.name().equals(colName) || prop.getName().toLowerCase().equals(colName)) {
				
				//
				for(final Persistable persistable : BEAN_CACHE) {
					
					//
					if(colType.equals(persistable.getClass())) {
						
						final Object valueCache = methodGet.invoke(persistable);
						
						//
						if(valueCache != null && valueCache.equals(value)) {
							
							return persistable;
						}
					}
				}
			}
		}
		
		return null;
	}
	
	/**
	 * @param autoCommit
	 * @return newConn(autoCommit) or getScopeConnection()
	 * @throws JXDaoException
	 */
	private Conn getConn(final boolean autoCommit) throws JXDaoException {
	
		try {
			//
			if(getLocalConnection() == null || getLocalConnection().isClosed()) {
				
				final Conn conn = newConn(autoCommit);
				
				return conn;
				
			}else {
				
				return getLocalConnection();
			}
			
		}catch(final SQLException sqlException) {
			
			throw newJXDaoException(null, sqlException);
		}
	}
	
	/**
	 * @param limit
	 * @param filter
	 * @param orderBy
	 * @param fetch
	 * @return 
	 * @throws JXDaoException
	 */
	private List<Bean> getFirstOrLast(final int limit, final Filterable filter, final OrderBy orderBy, final Class<?>... fetch) throws JXDaoException {
	
		List<Bean> list = null;
		
		// 
		if(filter != null) {
			
			// 
			if(filter.getOrderBy() != null) {
				
				throw new JXDaoException(I18N.DAO.anotherOrderByAlreadyImplemented());
			}
			
			filter.orderBy(orderBy);
			
			list = getByLimit(limit, filter, fetch);
			
		}else {
			
			list = getByLimit(limit, orderBy, fetch);
		}
		
		// 
		if(!orderBy.isAsc()) {
			
			Collections.reverse(list);
		}
		
		return list;
	}
	
	/**
	 * @param beans
	 * @param isSaveAction
	 * @return Map< Column Name, Primary Keys Values >
	 */
	private Map<String, List<Serializable>> getPKValues(final Collection<Bean> beans, final boolean isSaveAction) {
	
		final Map<String, List<Serializable>> pkValues = new HashMap<>();
		
		try {
			//
			for(final Bean bean : beans) {
				
				//
				if(bean != null) {
					
					//
					for(final MappedColumn mappedColumn : columnsMethods.getColumnMethod(bean.getClass())) {
						
						//
						if(mappedColumn.isPrimaryKey()) {
							
							//
							Object value = mappedColumn.getMethodGet().invoke(bean);
							
							//
							if(pkIsValid(value)) {
								
								List<Serializable> pks = new ArrayList<>();
								
								//
								if(Persistable.class.isAssignableFrom(value.getClass())) {
									
									//
									for(final MappedColumn colMethod : columnsMethods.getColumnMethod(value.getClass())) {
										
										//
										if(colMethod.isPrimaryKey()) {
											
											//
											if(isSaveAction && colMethod.isAutoIncrement()) {
												
												break;
											}
											
											value = colMethod.getMethodGet().invoke(value);
											
											//
											if(pkIsValid(value)) {
												
												//
												if(pkValues.containsKey(colMethod.getColumnName())) {
													
													pks = pkValues.get(colMethod.getColumnName());
													
												}else {
													
													pks = new ArrayList<>();
												}
												
												pks.add((Serializable)value);
												
												pkValues.put(colMethod.getColumnName(), pks);
											}
											
											break;
										}
									}
									
								}else {
									
									//
									if(pkValues.containsKey(mappedColumn.getColumnName())) {
										
										pks = pkValues.get(mappedColumn.getColumnName());
										
									}else {
										
										pks = new ArrayList<>();
									}
									
									pks.add((Serializable)value);
									
									pkValues.put(mappedColumn.getColumnName(), pks);
								}
							}
						}
					}
				}
			}
			
		}catch(final Throwable throwable) {
			
			throw new RuntimeException(throwable);
		}
		
		return pkValues;
	}
	
	/**
	 * @param manager
	 * @param beanClass
	 */
	@SuppressWarnings("unchecked")
	private void initGenericDao(final DBManager manager, final Class<? extends Persistable> beanClass) {
	
		ParameterCheck.isNull(manager, "manager");
		ParameterCheck.isNull(beanClass, "beanClass");
		
		this.manager = manager;
		this.beanClass = beanClass;
		
		//
		if(DBManager.getDaoCache().containsKey(beanClass)) {
			
			final GenericDao<?> genericDao = DBManager.getDaoCache().get(beanClass);
			
			tableName = genericDao.getTableName();
			columnsMethods = (MappedColumn)genericDao.columnsMethods;
			SQLs = genericDao.SQLs;
			BEAN_CACHE = genericDao.BEAN_CACHE;
			
		}else {
			
			//
			makeTableName(beanClass);
			makeColumnNames();
			
			//
			SQLs.put(OPERATIONS.DROP, DROP_TABLE.replace("@TBL", getTableName()));
			SQLs.put(OPERATIONS.DELETE, DELETE.replace("@TBL", getTableName()));
			SQLs.put(OPERATIONS.INSERT, INSERT.replace("@TBL", getTableName()).replace("@COL", columnsMethods.getSaveColumnsString()));
			SQLs.put(OPERATIONS.SELECT_COUNT, SELECT.replace("@COL", "COUNT(*)").replace("@TBL", getTableName()));
			SQLs.put(OPERATIONS.TRUNCATE, TRUNCATE.replace("@TBL", getTableName()));
			SQLs.put(OPERATIONS.UPDATE, UPDATE.replace("@TBL", getTableName()));
			
			final boolean isFirebirdOrSQLServer = getManager().getDBMS().isFirebird() || getManager().getDBMS().isSQLServer();
			
			SQLs.put(OPERATIONS.SELECT, SELECT.replace("@COL", (isFirebirdOrSQLServer ? "@limit " : "") + columnsMethods.getAllColumnsString()).replace("@TBL", getTableName()));
			
			//
			if(DBManager.isValidateDatabase()) {
				
				validateColumns();
			}
			
			DBManager.getDaoCache().put(beanClass, this);
		}
	}
	
	/**
	 *
	 */
	private void makeColumnNames() {
	
		try {
			// Para cada Propriedade o Bean
			for(final PropertyDescriptor propBean : Introspector.getBeanInfo(getBeanClass()).getPropertyDescriptors()) {
				
				//
				if( !propBean.getName().equals("class")) {
					
					final Method methodGetBean = propBean.getReadMethod();
					final Method methodSetBean = propBean.getWriteMethod();
					
					// Se possuir métodos de acesso
					if(methodGetBean != null && methodSetBean != null) {
						
						// Se estiver anotado como sendo Transiente, então o atributo não deve ser persistido
						if(methodGetBean.getAnnotation(Transient.class) != null) {
							
							continue;
						}
						
						// Se for uma Coleção de Objeto, assume-se existe uma outra Tabela que possui um FK ligado a essa Tabela
						if(TypeUtil.isCollection(propBean.getPropertyType())) {
							
							continue;
						}
						
						//
						boolean isAutoIncrement = false;
						boolean isForeignKey = false;
						boolean isPrimaryKey = false;
						boolean isUniqueKey = false;
						
						String columnName = null;
						
						//
						final DBColumn dbColumnBean = methodGetBean.getAnnotation(DBColumn.class);
						
						//
						if(dbColumnBean != null) {
							
							isAutoIncrement = dbColumnBean.isAutoIncrement();
							isForeignKey = dbColumnBean.isForeignKey();
							isPrimaryKey = dbColumnBean.isPrimaryKey();
							isUniqueKey = dbColumnBean.isPrimaryKey();
							
							//
							if(CheckUtil.isNotNullAndNotAbsoluteEmpty(dbColumnBean.name())) {
								
								columnName = dbColumnBean.name().trim();
							}
							
						}else {
							
							// Se for um Tipo Numérico e Inteiro
							if(TypeUtil.isInteger(propBean.getPropertyType())) {
								
								final String propBeanName = propBean.getName().toLowerCase();
								
								//
								if(propBeanName.equals("id")) {
									
									isPrimaryKey = true;
									isAutoIncrement = true;
									
								}else if(propBeanName.startsWith("fk_")) {
									
									isForeignKey = true;
								}
								
								// Se for uma Classe Persistível, assume-se que é uma FK
							}else if(Persistable.class.isAssignableFrom(propBean.getPropertyType())) {
								
								isForeignKey = true;
								
								final DBTable dbTable = propBean.getPropertyType().getAnnotation(DBTable.class);
								
								//
								if(dbTable != null && CheckUtil.isNotNullAndNotAbsoluteEmpty(dbTable.name())) {
									
									columnName = "fk_" + dbTable.name().trim();
									
								}else {
									
									columnName = "fk_" + propBean.getName().toLowerCase();
								}
								
								// Se for uma Classe Embarcada
							}else if(TypeUtil.isBean(propBean.getPropertyType())) {
								
								//
								for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(propBean.getPropertyType()).getPropertyDescriptors()) {
									
									//
									if( !propEmb.getName().equals("class")) {
										
										String columnNameEmb = propEmb.getName().toLowerCase();
										
										final Method methodGetEmb = propEmb.getReadMethod();
										final Method methodSetEmb = propEmb.getWriteMethod();
										
										//
										final MappedColumn colMethod = new MappedColumn();
										
										// Se for uma Classe Persistível, assume-se que é uma FK
										if(Persistable.class.isAssignableFrom(propEmb.getPropertyType())) {
											
											colMethod.setForeignKey(true);
											
											final DBTable dbTable = propEmb.getPropertyType().getAnnotation(DBTable.class);
											
											//
											if(dbTable != null && CheckUtil.isNotNullAndNotAbsoluteEmpty(dbTable.name())) {
												
												columnNameEmb = "fk_" + dbTable.name().trim();
												
											}else {
												
												columnNameEmb = "fk_" + columnNameEmb;
											}
										}
										
										//
										final DBColumn dbColumnEmb = methodGetEmb.getAnnotation(DBColumn.class);
										
										//
										if(dbColumnEmb != null) {
											
											colMethod.setAutoIncrement(dbColumnEmb.isAutoIncrement());
											colMethod.setPrimaryKey(dbColumnEmb.isPrimaryKey());
											colMethod.setUniqueKey(dbColumnEmb.isUniqueKey());
											
											//
											if(CheckUtil.isNotNullAndNotAbsoluteEmpty(dbColumnEmb.name())) {
												
												columnNameEmb = dbColumnEmb.name().trim();
											}
										}
										
										colMethod.setFieldName(propEmb.getName());
										colMethod.setBeanAttribute(propBean.getName());
										colMethod.setColumnName(columnNameEmb);
										colMethod.setMethodGet(methodGetEmb);
										colMethod.setMethodSet(methodSetEmb);
										colMethod.setOwnerType(propBean.getPropertyType());
										colMethod.setTypeInJava(methodGetEmb.getReturnType());
										colMethod.setTypeInSQL(TypeUtil.javaTypeToSqlType(methodGetEmb.getReturnType()));
										
										//
										columnsMethods.add(colMethod);
									}
								}
								
								continue;
							}
						}
						
						//
						if(columnName == null) {
							
							columnName = (isForeignKey ? "fk_" : "") + propBean.getName().toLowerCase();
						}
						
						//
						final MappedColumn colMethod = new MappedColumn();
						
						colMethod.setFieldName(propBean.getName());
						colMethod.setAutoIncrement(isAutoIncrement);
						colMethod.setColumnName(columnName);
						colMethod.setForeignKey(isForeignKey);
						colMethod.setMethodGet(methodGetBean);
						colMethod.setMethodSet(methodSetBean);
						colMethod.setPrimaryKey(isPrimaryKey);
						colMethod.setUniqueKey(isUniqueKey);
						colMethod.setOwnerType(getBeanClass());
						colMethod.setTypeInJava(methodGetBean.getReturnType());
						colMethod.setTypeInSQL(TypeUtil.javaTypeToSqlType(methodGetBean.getReturnType()));
						
						//
						columnsMethods.add(colMethod);
					}
				}
			}
			
		}catch(final IntrospectionException introspectionException) {
			
			introspectionException.printStackTrace();
		}
	}
	
	/**
	 * @param filter
	 * @return SELECT COUNT(*)...
	 * @throws JXDaoException
	 */
	private String makeCount(final Filterable filter) throws JXDaoException {
	
		return makeSQL(SQLs.get(OPERATIONS.SELECT_COUNT), filter);
	}
	
	/**
	 * @param filter
	 * @return DELETE ... < FILTER >
	 * @throws JXDaoException
	 */
	private String makeDelete(final Filterable filter) throws JXDaoException {
	
		return makeSQL(SQLs.get(OPERATIONS.DELETE), filter);
	}
	
	/**
	 * @param pkValues
	 *        is Map< Column Name, Primary Keys Values >
	 * @return {@link InFilter} or null
	 */
	private InFilter makePKInFilter(final Map<String, List<Serializable>> pkValues) {
	
		InFilter filter = null;
		
		//
		if( !columnsMethods.getPKColumns().isEmpty() && !pkValues.isEmpty()) {
			
			//
			for(final String pkColumn : pkValues.keySet()) {
				
				//
				if(filter == null) {
					
					filter = FilterUtil.in(pkColumn, pkValues.get(pkColumn));
					
				}else {
					
					filter.and(FilterUtil.in(pkColumn, pkValues.get(pkColumn)));
				}
			}
		}
		
		return filter;
	}
	
	/**
	 * @param pkValues
	 *        is Map< Column Name, Primary Keys Values >
	 * @return {@link NotInFilter} or null
	 */
	private NotInFilter makePKNotInFilter(final Map<String, List<Serializable>> pkValues) {
	
		NotInFilter filter = null;
		
		//
		if( !columnsMethods.getPKColumns().isEmpty() && !pkValues.isEmpty()) {
			
			//
			for(final String pkColumn : pkValues.keySet()) {
				
				//
				if(filter == null) {
					
					filter = FilterUtil.notIn(pkColumn, pkValues.get(pkColumn));
					
				}else {
					
					filter.and(FilterUtil.notIn(pkColumn, pkValues.get(pkColumn)));
				}
			}
		}
		
		return filter;
	}
	
	/**
	 * @param bean
	 * @param isInsert
	 * @return parameters to INSERT or UPDATE
	 * @throws Throwable
	 */
	private String makeSaveParameters(final Bean bean, final boolean isInsert) throws Throwable {
	
		final StringBuilder sql = new StringBuilder();
		
		//
		for(final String column : columnsMethods.getAllColumns()) {
			
			MappedColumn colMethod = columnsMethods.getColumnMethod(column, bean.getClass());
			
			String parameter = null;
			
			// Se não for uma Classe Embarcada
			if(colMethod != null) {
				
				// Se a coluna é AutoIncremento
				if(colMethod.isAutoIncrement()) {
					
					continue;
				}
				
				parameter = "?";
				
			}else {
				
				colMethod = columnsMethods.getColumnMethod(column);
				
				//
				for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
					
					//
					if(propEmb.getName().equals(colMethod.getBeanAttribute())) {
						
						parameter = "?";
						
						break;
					}
				}
			}
			
			//
			if(parameter != null) {
				
				sql.append(sql.length() > 0 ? ", " : "");
				sql.append( !isInsert ? column + " = " : "");
				sql.append(parameter);
			}
		}
		
		return sql.toString();
	}
	
	/**
	 * @param bean
	 * @param isInsert
	 * @param filter
	 * @return UPDATE ... < COLUMN = VALUE > ... < FILTER >
	 * @throws Throwable
	 */
	private String makeSaveParameters(final Bean bean, final boolean isInsert, final Filterable filter) throws Throwable {
	
		//
		if(isInsert) {
			
			return SQLs.get(OPERATIONS.INSERT).replace("@VLS", makeSaveParameters(bean, true));
			
		}else {
			
			return makeSQL(SQLs.get(OPERATIONS.UPDATE).replace("@VLS", makeSaveParameters(bean, false)), filter);
		}
	}
	
	/**
	 * @param filter
	 * @param limit
	 * @return SELECT ... < FILTER >
	 * @throws JXDaoException
	 */
	private String makeSelect(final Filterable filter, final int limit) throws JXDaoException {
	
		String strLimit = "";
		
		//
		if(limit > 0) {
			
			//
			if(getManager().getDBMS().isFirebird()) {
				
				strLimit = limit > 0 ? " FIRST " + limit : "";
				
			}else if(getManager().getDBMS().isMySQL()) {
				
				strLimit = limit > 0 ? " \nLIMIT " + limit : "";
				
			}else if(getManager().getDBMS().isOracle()) {
				
				strLimit = limit > 0 ? " \nROWNUM <= " + limit : "";
				
			}else if(getManager().getDBMS().isPostgreSQL()) {
				
				strLimit = limit > 0 ? " \nLIMIT " + limit : "";
				
			}else if(getManager().getDBMS().isSQLServer()) {
				
				strLimit = limit > 0 ? " TOP " + limit : "";
			}
		}
		
		//
		if(getManager().getDBMS().isFirebird() || getManager().getDBMS().isSQLServer()) {
			
			return makeSQL(SQLs.get(OPERATIONS.SELECT).replace(" @limit", strLimit), filter);
			
		}else {
			
			String sql = makeSQL(SQLs.get(OPERATIONS.SELECT), filter);
			
			final int lastIndexOf = sql.lastIndexOf(";");
			
			//
			if(lastIndexOf >= 0) {
				
				sql = sql.substring(0, lastIndexOf);
			}
			
			return sql.trim() + strLimit + ";";
		}
	}
	
	/**
	 * @param sql
	 * @param filter
	 * @return SQL
	 * @throws JXDaoException
	 */
	private String makeSQL(final String sql, final Filterable filter) throws JXDaoException {
	
		String strFilter = "";
		
		//
		if(filter != null) {
			
			/*
			 * TODO: Melhorar/Implementar TRAVA contra SQL-Injection transformar o "strFilter" em um SQL que possa ser utilizado pelo "PreparedStatement"
			 * 
			 * http://www.applicure.com/blog/owasp-top-10-2010
			 */
			
			strFilter = filter.toString().trim();
			
			//
			for(String str : strFilter.split(";")) {
				
				String sqlAux = str.trim().toUpperCase();
				
				//
				if(OPERATIONS.valueStartsWithAnyOperation(sqlAux)) {
					
					JXDaoException exception = new JXDaoException(I18N.DAO.operationSuspected());
					
					exception.setSQL(sqlAux);
					
					throw exception;
				}
			}
			
			//
			if( !strFilter.startsWith("GROUP BY") && !strFilter.startsWith("ORDER BY")) {
				
				strFilter = "\nWHERE " + strFilter;
				
			}else if( !strFilter.startsWith("\n")) {
				
				strFilter = "\n" + strFilter;
			}
		}
		
		return sql.replace("\n@CNDT", strFilter);
	}
	
	/**
	 * @param clazz
	 */
	private void makeTableName(final Class<?> clazz) {
	
		//
		if(clazz != null) {
			
			final DBTable dbTable = clazz.getAnnotation(DBTable.class);
			
			//
			if(dbTable == null) {
				
				makeTableName(clazz.getSuperclass());
				
			}else {
				
				schemaName = CheckUtil.isNotNullAndNotAbsoluteEmpty(dbTable.schema()) ? dbTable.schema().trim() + "." : "";
				tableName = CheckUtil.isNotNullAndNotAbsoluteEmpty(dbTable.name()) ? dbTable.name().trim() : "";
			}
			
			//
			if(CheckUtil.isNullOrAbsoluteEmpty(tableName)) {
				
				tableName = getBeanClass().getSimpleName().toLowerCase();
			}
			
			tableName = schemaName + tableName;
		}
	}
	
	/**
	 * @param primaryKey
	 * @return true if primaryKey is valid
	 */
	private boolean pkIsValid(final Object primaryKey) {
	
		return 
			!(
				primaryKey == null || 
				(primaryKey instanceof String && CheckUtil.isAbsoluteEmpty((String)primaryKey)) || 
				(TypeUtil.isInteger(primaryKey.getClass()) && new BigInteger(primaryKey.toString()).longValue() < 1)
			);
	}
	
	/**
	 * @param statement
	 * @param bean
	 * @param isInsert
	 * @throws Throwable
	 */
	private void setSaveParameters(final PreparedStatement statement, final Bean bean, final boolean isInsert) throws Throwable {
	
		int parameterIndex = 0;
		
		//
		for(final String column : columnsMethods.getAllColumns()) {
			
			MappedColumn colMethod = columnsMethods.getColumnMethod(column, bean.getClass());
			
			Object value = null;
			
			// Se não for uma Classe Embarcada
			if(colMethod != null) {
				
				// Se a coluna é AutoIncremento
				if(colMethod.isAutoIncrement()) {
					
					continue;
				}
				
				value = colMethod.getMethodGet().invoke(bean);
				
				//
				if(value != null) {
					
					// Se for uma Classe Persistível
					if(Persistable.class.isAssignableFrom(value.getClass())) {
						
						boolean valueOK = false;
						
						//
						for(final PropertyDescriptor propBean : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
							
							final Method methodGet = propBean.getReadMethod();
							
							//
							if(methodGet != null && methodGet.getName().equals(colMethod.getMethodGet().getName())) {
								
								final DBColumn dbColumn = methodGet.getAnnotation(DBColumn.class);
								
								final String propEmbName = dbColumn != null && CheckUtil.isNotNullAndNotAbsoluteEmpty(dbColumn.foreignKeyWith()) ? dbColumn.name().trim() : "id";
								
								//
								for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(value.getClass()).getPropertyDescriptors()) {
									
									//
									if(propEmb.getName().equals(propEmbName)) {
										
										final Method methodGetEmb = propEmb.getReadMethod();
										
										value = methodGetEmb.invoke(value);
										
										valueOK = true;
										
										break;
									}
								}
							}
							
							//
							if(valueOK) {
								
								break;
							}
						}
					}
				}
				
			}else {
				
				colMethod = columnsMethods.getColumnMethod(column);
				
				//
				for(final PropertyDescriptor propEmb : Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors()) {
					
					//
					if(propEmb.getName().equals(colMethod.getBeanAttribute())) {
						
						// Obtém o Objeto Embarcado
						final Object objectEmb = propEmb.getReadMethod().invoke(bean);
						
						//
						if(objectEmb != null) {
							
							value = colMethod.getMethodGet().invoke(objectEmb);
							
							//
							if(value != null) {
								
								// Se for uma Classe Persistível
								if(Persistable.class.isAssignableFrom(value.getClass())) {
									
									boolean valueOK = false;
									
									//
									for(final PropertyDescriptor propBean : Introspector.getBeanInfo(objectEmb.getClass()).getPropertyDescriptors()) {
										
										final Method methodGet = propBean.getReadMethod();
										
										//
										if(methodGet != null && methodGet.getName().equals(colMethod.getMethodGet().getName())) {
											
											final DBColumn dbColumn = methodGet.getAnnotation(DBColumn.class);
											
											final String propEmbName = dbColumn != null && CheckUtil.isNotNullAndNotAbsoluteEmpty(dbColumn.name()) ? dbColumn.name().trim() : "id";
											
											//
											for(final PropertyDescriptor propEmb2 : Introspector.getBeanInfo(value.getClass()).getPropertyDescriptors()) {
												
												//
												if(propEmb2.getName().equals(propEmbName)) {
													
													final Method methodGetEmb = propEmb2.getReadMethod();
													
													value = methodGetEmb.invoke(value);
													
													valueOK = true;
													
													break;
												}
											}
										}
										
										//
										if(valueOK) {
											
											break;
										}
									}
								}
							}
						}
						
						break;
					}
				}
			}
			
			//
			if(value == null) {
				
				statement.setNull(++parameterIndex, colMethod.getTypeInSQL());
				
			}else {
				
				statement.setObject(++parameterIndex, value, colMethod.getTypeInSQL());
			}
		}
	}
	
	/**
	 * Verifica se todas as Colunas geradas são Válidas
	 */
	private void validateColumns() {
	
		try {
			//
			final String sql = makeSelect(null, 1);
			
			final ResultSet resultSet = getManager().getDBMS().isDerby() ? newConn().executeQuery(sql, 1) : newConn().executeQuery(sql);
			
			final Bean bean = fillBean(resultSet);
			
			//
			if(bean != null) {
				
				validateBean(bean);
			}
			
		}catch(final Throwable throwable) {
			
			throw new RuntimeException(I18N.DAO.inTable(tableName) + ": " + throwable.getMessage(), throwable);
		}
	}
	
	/**
	 * @param clazz
	 * @param fetch
	 * @return true if fetch array contais clazz
	 */
	protected final boolean fetchClass(final Class<?> clazz, final Class<?>[] fetch) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(fetch)) {
			
			//
			for(final Class<?> c : fetch) {
				
				//
				if(c.equals(clazz)) {
					
					return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * @param bean
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @throws JXDaoException
	 */
	protected void fillChildren(final Bean bean, final Class<?>... fetch) throws JXDaoException {
	
		// Not Implemented
	}
	
	/**
	 * @param sql
	 * @param throwable
	 * @return {@link JXDaoException}
	 */
	protected final JXDaoException newJXDaoException(final String sql, final Throwable throwable) {
	
		final JXDaoException jxDaoException = new JXDaoException(throwable);
		
		jxDaoException.setSQL(sql);
		
		return jxDaoException;
	}
	
	/**
	 * @param bean
	 * @param conn
	 * @param fetch
	 *        Example: {ObjA.class, ObjB.class, ObjC.class}
	 * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements that return nothing
	 * @throws JXDaoException
	 */
	protected int saveChildren(final Bean bean, final Conn conn, final Class<?>... fetch) throws JXDaoException {
	
		// Not Implemented
		return 0;
	}
	
	/**
	 *
	 */
	@Override
	public final void abortOperation() {
	
		abortOperation = true;
	}
	
	/**
	 * @throws JXDaoException
	 */
	@Override
	public final void beginTransaction() throws JXDaoException {
	
		// Se já existe uma Transaction aberta
		if(transactionIsOpen()) {
			
			throw new JXDaoException(I18N.DAO.transactionAlreadyOpen());
		}
		
		localConnection = newConn(false);
	}
	
	/**
	 * @return cache size
	 */
	public final int cacheSize() {
	
		return BEAN_CACHE.size();
	}
	
	/**
	 *
	 */
	public final void clearCache() {
	
		BEAN_CACHE.clear();
	}
	
	/**
	 * @throws JXDaoException
	 */
	@Override
	public final void commitTransaction() throws JXDaoException {
	
		try {
			//
			if(transactionIsOpen() && !getLocalConnection().isAutoCommit()) {
				
				getLocalConnection().commit();
				
				localConnection = null;
			}
			
		}catch(final SQLException sqlException) {
			
			throw newJXDaoException(null, sqlException);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#count()
	 */
	@Override
	public final long count() throws JXDaoException {
	
		return count(null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#count()
	 */
	@Override
	public final long count(final Filterable filter) throws JXDaoException {
	
		abortOperation = false;
		
		Conn conn = null;
		
		String sql = null;
		
		long result = 0L;
		
		try {
			//
			conn = getConn(false);
			
			//
			sql = makeCount(filter);
			
			final ResultSet resultSet = conn.executeQuery(sql);
			
			//
			if(resultSet.next()) {
				
				result = resultSet.getInt(1);
			}
			
			// Se é uma transação local, efetiva as operações realizadas
			if(getLocalConnection() == null) {
				
				//
				if(isOperationWasAborted()) {
					
					System.out.println(I18N.DAO.operationAborted());
					
					try {
						//
						conn.rollback();
						
					}catch(final JXDaoException jxDaoException) {
						
						throw newJXDaoException(sql, jxDaoException);
					}
					
					return 0L;
					
				}else {
					
					conn.commit();
				}
			}
			
			return result;
			
		}catch(final Throwable exception) {
			
			//
			if(conn != null) {
				
				try {
					//
					conn.rollback();
					
				}catch(final JXDaoException jxDaoException) {
					
					throw newJXDaoException(sql, jxDaoException);
				}
			}
			
			throw newJXDaoException(sql, exception);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteAll()
	 */
	@Override
	public final int deleteAll() throws JXDaoException {
	
		return deleteByFilter(null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteByFilter(org.jxDao.dao.filter.Filterable)
	 */
	@Override
	public final int deleteByFilter(final Filterable filter) throws JXDaoException {
	
		return executeUpdate(makeDelete(filter));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteByPK(org.jxDao.dao.Persistable)
	 */
	@Override
	public final int deleteByPK(final Bean bean) throws JXDaoException {
	
		ParameterCheck.isNull(bean, "bean");
		
		return deleteByPK(Arrays.asList(bean));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteByPK(org.jxDao.dao.Persistable[])
	 */
	@Override
	public final int deleteByPK(final Bean[] beans) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return deleteByPK(Arrays.asList(beans));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteByPK(java.util.Collection)
	 */
	@Override
	public final int deleteByPK(final Collection<Bean> beans) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(beans, false));
		
		return filter != null ? deleteByFilter(filter) : 0;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteByPKNotIn(org.jxDao.dao.Persistable[])
	 */
	@Override
	public int deleteByPKNotIn(final Bean[] beans) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return deleteByPKNotIn(Arrays.asList(beans));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#deleteWherePKNotIn(java.util.Collection)
	 */
	@Override
	public int deleteByPKNotIn(final Collection<Bean> beans) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(beans, false));
		
		return filter != null ? deleteByFilter(filter) : 0;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#dropTable()
	 */
	@Override
	public final int dropTable() throws JXDaoException {
	
		return executeUpdate(SQLs.get(OPERATIONS.DROP));
	}
	
	/**
	 * @param columnName
	 * @param pkValues
	 * @return the beans
	 */
	public final List<Bean> fillPK(final String columnName, final Object[] pkValues) {
	
		ParameterCheck.isNullOrAbsoluteEmpty(columnName, "columnName");
		ParameterCheck.isNullOrEmpty(pkValues, "pkValues");
		
		final List<Bean> beans = new ArrayList<>();
		
		try {
			//
			final MappedColumn colMethod = columnsMethods.getColumnMethod(columnName, getBeanClass());
			
			// Se não for uma Classe Embarcada
			if(colMethod != null && colMethod.isPrimaryKey()) {
				
				//
				for(final Object pk : pkValues) {
					
					//
					if(pkIsValid(pk)) {
						
						@SuppressWarnings("unchecked")
						final Bean bean = (Bean)getBeanClass().newInstance();
						
						colMethod.getMethodSet().invoke(bean, pk);
						
						beans.add(bean);
					}
				}
			}
			
		}catch(final Throwable throwable) {
			
			throw new RuntimeException(throwable);
		}
		
		return beans;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#fillWithFetch(org.jxDao.dao.Persistable, java.lang.Class[])
	 */
	@Override
	public Bean fillWithFetch(Bean bean, Class<?>... fetch) throws JXDaoException {
	
		//
		for(MappedColumn mappedColumn : columnsMethods.getFKMappedColumns()) {
			
			//
			if(fetchClass(mappedColumn.getTypeInJava(), fetch)) {
				
				try {
					//
					Persistable fkBean = (Persistable)mappedColumn.getMethodGet().invoke(bean);
					
					fkBean = fetchByPKAndClass(fkBean, fetch);
					
					mappedColumn.getMethodSet().invoke(bean, fkBean);
					
				}catch(Throwable throwable) {
					
					throw new JXDaoException(throwable);
				}
			}
		}
		
		// 
		fillChildren(bean, fetch);
		
		return bean;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getAll(java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getAll(final Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(-1, (Filterable)null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getAll(org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getAll(GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		return getByFilter(FilterUtil.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getAll(org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getAll(GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		return getByFilter(FilterUtil.groupByOrderBy(groupBy, orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getAll(org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getAll(OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		return getByFilter(FilterUtil.orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getBeanClass()
	 */
	@Override
	public final Class<?> getBeanClass() {
	
		return beanClass;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByFilterable(org.jxDao.dao.filter.Filterable)
	 */
	@Override
	public final List<Bean> getByFilter(final Filterable filter) throws JXDaoException {
	
		return getByLimit(-1, filter);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByFilter(org.jxDao.dao.filter.Filterable, java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getByFilter(final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(-1, filter, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByLimit(int, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByLimit(int limit, Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(limit, (Filterable)null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByLimit(int, org.jxDao.dao.filter.Filterable, java.lang.Class[])
	 */
	@Override
	public final List<Bean> getByLimit(final int limit, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		abortOperation = false;
		
		Conn conn = null;
		String sql = null;
		
		List<Bean> result = null;
		
		try {
			//
			conn = getConn(false);
			
			//
			sql = makeSelect(filter, limit);
			
			//
			if(getManager().getDBMS().isDerby()) {
				
				result = fillList(conn.executeQuery(sql, limit), fetch);
				
			}else {
				
				result = fillList(conn.executeQuery(sql), fetch);
			}
			
			// Se é uma transação local, efetiva as operações realizadas
			if(getLocalConnection() == null) {
				
				//
				if(isOperationWasAborted()) {
					
					System.out.println(I18N.DAO.operationAborted());
					
					try {
						//
						conn.rollback();
						
					}catch(final JXDaoException jxDaoException) {
						
						throw newJXDaoException(sql, jxDaoException);
					}
					
					return new ArrayList<Bean>();
					
				}else {
					
					conn.commit();
				}
			}
			
			return result;
			
		}catch(final Throwable exception) {
			
			//
			if(conn != null) {
				
				try {
					//
					conn.rollback();
					
				}catch(final JXDaoException jxDaoException) {
					
					throw newJXDaoException(sql, jxDaoException);
				}
			}
			
			throw newJXDaoException(sql, exception);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByLimit(int, org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByLimit(int limit, GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(limit, FilterUtil.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByLimit(int, org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByLimit(int limit, GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(limit, FilterUtil.groupByOrderBy(groupBy, orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByLimit(int, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByLimit(int limit, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		return getByLimit(limit, FilterUtil.orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(org.jxDao.dao.Persistable, java.lang.Class<?>[])
	 */
	@Override
	public final Bean getByPK(final Bean bean, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNull(bean, "bean");
		
		final List<Bean> beans = getByPK(Arrays.asList(bean), fetch);
		
		return !beans.isEmpty() ? beans.get(0) : null;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(org.jxDao.dao.Persistable[], java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getByPK(final Bean[] beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return getByPK(Arrays.asList(beans), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Bean[] beans, GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Bean[] beans, GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.groupBy(groupBy).orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Bean[] beans, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(java.util.Collection, java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getByPK(final Collection<Bean> beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(beans, false));
		
		return filter != null ? getByFilter(filter, fetch) : new ArrayList<Bean>();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(java.util.Collection, org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Collection<Bean> beans, GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(java.util.Collection, org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Collection<Bean> beans, GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.groupBy(groupBy).orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPK(java.util.Collection, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPK(Collection<Bean> beans, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final InFilter filter = makePKInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(org.jxDao.dao.Persistable[], java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getByPKNotIn(final Bean[] beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return getByPKNotIn(Arrays.asList(beans), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Bean[] beans, GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Bean[] beans, GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.groupBy(groupBy).orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Bean[] beans, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(Arrays.asList(beans), false));
		
		return getByFilter(filter.orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(java.util.Collection, java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getByPKNotIn(final Collection<Bean> beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(beans, false));
		
		return filter != null ? getByFilter(filter, fetch) : new ArrayList<Bean>();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(java.util.Collection, org.jxDao.dao.filter.GroupBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Collection<Bean> beans, GroupBy groupBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.groupBy(groupBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(java.util.Collection, org.jxDao.dao.filter.GroupBy, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Collection<Bean> beans, GroupBy groupBy, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.groupBy(groupBy).orderBy(orderBy), fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getByPKNotIn(java.util.Collection, org.jxDao.dao.filter.OrderBy, java.lang.Class[])
	 */
	@Override
	public List<Bean> getByPKNotIn(Collection<Bean> beans, OrderBy orderBy, Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		final NotInFilter filter = makePKNotInFilter(getPKValues(beans, false));
		
		return getByFilter(filter.orderBy(orderBy), fetch);
	}
	
	/**
	 * @param fieldName
	 * @return the column name or null
	 */
	public final String getColumnByField(final String fieldName) {
	
		return columnsMethods.getColumnNameByFieldName(fieldName);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getFirst(int, int)
	 */
	@Override
	public final List<Bean> getFirst(final int limit, final Class<?>... fetch) throws JXDaoException {
	
		return getFirst(limit, null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getFirst(int, org.jxDao.dao.filter.Filterable, java.lang.Class[])
	 */
	@Override
	public List<Bean> getFirst(final int limit, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		final OrderBy orderBy = new OrderBy(columnsMethods.getPKColumns());
		
		return getFirstOrLast(limit, filter, orderBy, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getLast(int, java.lang.Class<?>[])
	 */
	@Override
	public final List<Bean> getLast(final int limit, final Class<?>... fetch) throws JXDaoException {
	
		return getLast(limit, null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#getLast(int, org.jxDao.dao.filter.Filterable, java.lang.Class[])
	 */
	@Override
	public List<Bean> getLast(final int limit, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		final OrderBy orderBy = new OrderBy(columnsMethods.getPKColumns(), false);
		
		return getFirstOrLast(limit, filter, orderBy, fetch);
	}
	
	/**
	 * @return localConnection
	 */
	public final Conn getLocalConnection() {
	
		return localConnection;
	}
	
	/**
	 * @return manager
	 */
	public final DBManager getManager() {
	
		return manager;
	}
	
	/**
	 * @param beans
	 * @param isSaveAction
	 * @return Map< Column Name, Primary Keys Values >
	 */
	public final Map<String, List<Serializable>> getPKValues(final Collection<Bean> beans) {
	
		return getPKValues(beans, false);
	}
	
	/**
	 * @return the schemaName
	 */
	public final String getSchemaName() {
	
		return schemaName;
	}
	
	/**
	 * @return the tableName
	 */
	public final String getTableName() {
	
		return tableName;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#isOperationWasAborted()
	 */
	@Override
	public final boolean isOperationWasAborted() {
	
		return abortOperation;
	}
	
	/**
	 * autoCommit is true initConnection is true
	 * 
	 * @return {@link Conn}
	 * @throws JXDaoException
	 */
	public final Conn newConn() throws JXDaoException {
	
		return newConn(true);
	}
	
	/**
	 * initConnection is true
	 * 
	 * @param autoCommit
	 * @return {@link Conn}
	 * @throws JXDaoException
	 */
	public final Conn newConn(final boolean autoCommit) throws JXDaoException {
	
		try {
			//
			return getManager().newConn(autoCommit);
			
		}catch(final JXDaoException jxDaoException) {
			
			throw newJXDaoException(null, jxDaoException);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#rollbackTransaction()
	 */
	@Override
	public final void rollbackTransaction() throws JXDaoException {
	
		try {
			//
			if(transactionIsOpen() && !getLocalConnection().isAutoCommit()) {
				
				getLocalConnection().rollback();
				
				localConnection = null;
			}
			
		}catch(final SQLException sqlException) {
			
			throw newJXDaoException(null, sqlException);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(org.jxDao.dao.Persistable, java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Bean bean, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNull(bean, "bean");
		
		return save(bean, null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(org.jxDao.dao.Persistable, org.jxDao.dao.filter.Filterable, java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Bean bean, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNull(bean, "bean");
		
		return save(Arrays.asList(bean), filter, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(org.jxDao.dao.Persistable[], java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Bean[] beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return save(beans, null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(org.jxDao.dao.Persistable[], org.jxDao.dao.filter.Filterable, java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Bean[] beans, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return save(Arrays.asList(beans), filter, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(java.util.Collection, java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Collection<Bean> beans, final Class<?>... fetch) throws JXDaoException {
	
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		return save(beans, null, fetch);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#save(java.util.Collection, org.jxDao.dao.filter.Filterable, java.lang.Class<?>[])
	 */
	@Override
	public final int save(final Collection<Bean> beans, final Filterable filter, final Class<?>... fetch) throws JXDaoException {
	
		abortOperation = false;
		
		ParameterCheck.isNullOrEmpty(beans, "beans");
		
		Conn conn = null;
		
		String sql = null;
		
		int result = 0;
		
		try {
			//
			conn = getConn(false);
			
			//
			for(final Bean bean : beans) {
				
				//
				if(isOperationWasAborted()) {
					
					System.out.println(I18N.DAO.operationAborted());
					
					try {
						//
						conn.rollback();
						
					}catch(final JXDaoException jxDaoException) {
						
						throw newJXDaoException(sql, jxDaoException);
					}
					
					return 0;
				}
				
				//
				if(bean != null) {
					
					boolean isInsert = true;
					
					// Lança RuntimeException caso algum atributo não possua valor válido
					validateBean(bean);
					
					// Retorna um Filtro contendo as PK ou null
					Filterable saveFilter = makePKInFilter(getPKValues(Arrays.asList(bean), true));
					
					// Se a(s) PK(s) possuem valores, então é um UPDATE
					if(saveFilter != null) {
						
						isInsert = false;
						
						// Se mais algum Filtro foi passado, adiciona no Filtro de PK(s)
						if(CheckUtil.isNotNullAndNotEmpty(filter)) {
							
							saveFilter.and(filter);
						}
						
					}else if(CheckUtil.isNotNullAndNotEmpty(filter)) {
						
						isInsert = false;
						
						saveFilter = filter;
					}
					
					//
					sql = makeSaveParameters(bean, isInsert, saveFilter);
					
					PreparedStatement statement;
					
					//
					if(isInsert) {
						
						statement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
						
					}else {
						
						statement = conn.prepareStatement(sql);
					}
					
					//
					setSaveParameters(statement, bean, isInsert);
					
					int currentResult = statement.executeUpdate();
					
					result += currentResult;
					
					//
					if(isInsert) {
						
						final ResultSet resultSet = statement.getGeneratedKeys();
						
						fillBean(resultSet, bean, true);
						
						resultSet.close();
					}
					
					//
					if(currentResult > 0) {
						
						saveChildren(bean, conn, fetch);
					}
				}
			}
			
			// Se é uma transação local, efetiva as operações realizadas
			if(getLocalConnection() == null) {
				
				//
				if(isOperationWasAborted()) {
					
					System.out.println(I18N.DAO.operationAborted());
					
					try {
						//
						conn.rollback();
						
					}catch(final JXDaoException jxDaoException) {
						
						throw newJXDaoException(sql, jxDaoException);
					}
					
					return 0;
					
				}else {
					
					conn.commit();
				}
			}
			
			return result;
			
		}catch(final Throwable throwable) {
			
			//
			if(conn != null) {
				
				try {
					//
					conn.rollback();
					
				}catch(final JXDaoException jxDaoException) {
					
					throw newJXDaoException(sql, jxDaoException);
				}
			}
			
			throw newJXDaoException(sql, throwable);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#transactionIsOpen()
	 */
	@Override
	public final boolean transactionIsOpen() throws JXDaoException {
	
		try {
			//
			return getLocalConnection() != null && getLocalConnection().isOpen() && getLocalConnection().transactionIsOpen();
			
		}catch(final SQLException sqlException) {
			
			throw newJXDaoException(null, sqlException);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxDao.dao.Dao#truncate(boolean)
	 */
	@Override
	public final int truncate(final boolean cascade) throws JXDaoException {
	
		return executeUpdate(SQLs.get(OPERATIONS.TRUNCATE).replace(" @KSKD", cascade ? " CASCADE" : ""));
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxDao
	 * @package org.jxDao.dao
	 * @date 23/04/2014 - 12:17:34
	 */
	private final class MappedColumn implements Comparable<MappedColumn> {
		
		private final Set<MappedColumn> mappedColumns = new TreeSet<>();
		
		private Class<?> ownerType = null;
		
		private String fieldName = null;
		
		private String beanAttribute = null;
		
		private String columnName = null;
		
		private Class<?> typeInJava = null;
		
		private int typeInSQL;
		
		private Method methodGet = null;
		
		private Method methodSet = null;
		
		private boolean autoIncrement = false;
		
		private boolean foreignKey = false;
		
		private boolean primaryKey = false;
		
		private boolean uniqueKey = false;
		
		/**
		 *
		 */
		private MappedColumn() {
		
		}
		
		/**
		 * @param mappedColumn
		 */
		private void add(final MappedColumn mappedColumn) {
		
			mappedColumns.add(mappedColumn);
		}
		
		/**
		 * @return the allColumns
		 */
		private List<String> getAllColumns() {
		
			final List<String> allColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				allColumns.add(mappedColumn.getColumnName());
			}
			
			return allColumns;
		}
		
		/**
		 * @return the allColumns
		 */
		private String getAllColumnsString() {
		
			return getAllColumns().toString().replace("[", "").replace("]", "");
		}
		
		/**
		 * @return the beanAttribute
		 */
		private String getBeanAttribute() {
		
			return beanAttribute;
		}
		
		/**
		 * @param ownerType
		 * @return List< {@link MappedColumn} >
		 */
		private List<MappedColumn> getColumnMethod(final Class<?> ownerType) {
		
			final List<MappedColumn> colMethods = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.getOwnerType().equals(ownerType)) {
					
					colMethods.add(mappedColumn);
				}
			}
			
			return colMethods;
		}
		
		/**
		 * @param columnName
		 * @param clazz
		 * @return the mappedColumn or null
		 */
		private MappedColumn getColumnMethod(final String columnName) {
		
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.getColumnName().equals(columnName)) {
					
					return mappedColumn;
				}
			}
			
			return null;
		}
		
		/**
		 * @param columnName
		 * @param ownerType
		 * @return the mappedColumn or null
		 */
		private MappedColumn getColumnMethod(final String columnName, final Class<?> ownerType) {
		
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.getColumnName().equals(columnName) && mappedColumn.getOwnerType().equals(ownerType)) {
					
					return mappedColumn;
				}
			}
			
			return null;
		}
		
		/**
		 * @return the columnName
		 */
		private String getColumnName() {
		
			return columnName;
		}
		
		/**
		 * @param fieldName
		 * @return the column name or null
		 */
		private String getColumnNameByFieldName(final String fieldName) {
		
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.getFieldName().equalsIgnoreCase(fieldName)) {
					
					return mappedColumn.getColumnName();
				}
			}
			
			return null;
		}
		
		/**
		 * @return the fieldName
		 */
		private String getFieldName() {
		
			return fieldName;
		}
		
		/**
		 * @return the fkColumns
		 */
		@SuppressWarnings("unused")
		private List<String> getFKColumns() {
		
			final List<String> fkColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.isForeignKey()) {
					
					fkColumns.add(mappedColumn.getColumnName());
				}
			}
			
			return fkColumns;
		}
		
		/**
		 * @return the fkMappedColumns
		 */
		private List<MappedColumn> getFKMappedColumns() {
		
			final List<MappedColumn> fkMappedColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.isForeignKey()) {
					
					fkMappedColumns.add(mappedColumn);
				}
			}
			
			return fkMappedColumns;
		}
		
		/**
		 * @return the methodGet
		 */
		private Method getMethodGet() {
		
			return methodGet;
		}
		
		/**
		 * @return the methodSet
		 */
		private Method getMethodSet() {
		
			return methodSet;
		}
		
		/**
		 * @return the columns aren't Primary Key AND aren't Foreign Key
		 */
		@SuppressWarnings("unused")
		private List<String> getOtherColumns() {
		
			final List<String> saveColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if( !mappedColumn.isPrimaryKey() && !mappedColumn.isForeignKey()) {
					
					saveColumns.add(mappedColumn.getColumnName());
				}
			}
			
			return saveColumns;
		}
		
		/**
		 * @return the ownerType
		 */
		private Class<?> getOwnerType() {
		
			return ownerType;
		}
		
		/**
		 * @return the pkColumns
		 */
		private List<String> getPKColumns() {
		
			final List<String> pkColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.isPrimaryKey()) {
					
					pkColumns.add(mappedColumn.getColumnName());
				}
			}
			
			return pkColumns;
		}
		
		/**
		 * @return the columns with autoIncrement == false
		 */
		private List<String> getSaveColumns() {
		
			final List<String> saveColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if( !mappedColumn.isAutoIncrement()) {
					
					saveColumns.add(mappedColumn.getColumnName());
				}
			}
			
			return saveColumns;
		}
		
		/**
		 * @return the columns with autoIncrement == false
		 */
		private String getSaveColumnsString() {
		
			return getSaveColumns().toString().replace("[", "").replace("]", "");
		}
		
		/**
		 * @return the type
		 */
		private Class<?> getTypeInJava() {
		
			return typeInJava;
		}
		
		/**
		 * @return the typeInSQL
		 */
		private int getTypeInSQL() {
		
			return typeInSQL;
		}
		
		/**
		 * @return the uniqueKeyColumns
		 */
		@SuppressWarnings("unused")
		private List<String> getUniqueKeyColumns() {
		
			final List<String> uniqueKeyColumns = new ArrayList<>();
			
			for(final MappedColumn mappedColumn : mappedColumns) {
				
				if(mappedColumn.isUniqueKey()) {
					
					uniqueKeyColumns.add(mappedColumn.getColumnName());
				}
			}
			
			return uniqueKeyColumns;
		}
		
		/**
		 * @return the autoIncrement
		 */
		private boolean isAutoIncrement() {
		
			return autoIncrement;
		}
		
		/**
		 * @return the foreignKey
		 */
		private boolean isForeignKey() {
		
			return foreignKey;
		}
		
		/**
		 * @return the primaryKey
		 */
		private boolean isPrimaryKey() {
		
			return primaryKey;
		}
		
		/**
		 * @return the uniqueKey
		 */
		
		private boolean isUniqueKey() {
		
			return uniqueKey;
		}
		
		/**
		 * @param autoIncrement
		 *        the autoIncrement to set
		 */
		private void setAutoIncrement(final boolean autoIncrement) {
		
			this.autoIncrement = autoIncrement;
		}
		
		/**
		 * @param beanAttribute
		 *        the beanAttribute to set
		 */
		private void setBeanAttribute(final String beanAttribute) {
		
			this.beanAttribute = beanAttribute;
		}
		
		/**
		 * @param columnName
		 *        the columnName to set
		 */
		private void setColumnName(final String columnName) {
		
			this.columnName = columnName;
		}
		
		/**
		 * @param fieldName
		 *        the fieldName to set
		 */
		private void setFieldName(final String fieldName) {
		
			this.fieldName = fieldName;
		}
		
		/**
		 * @param foreignKey
		 *        the foreignKey to set
		 */
		private void setForeignKey(final boolean foreignKey) {
		
			this.foreignKey = foreignKey;
		}
		
		/**
		 * @param methodGet
		 *        the methodGet to set
		 */
		private void setMethodGet(final Method methodGet) {
		
			this.methodGet = methodGet;
		}
		
		/**
		 * @param methodSet
		 *        the methodSet to set
		 */
		private void setMethodSet(final Method methodSet) {
		
			this.methodSet = methodSet;
		}
		
		/**
		 * @param ownerType
		 *        the ownerType to set
		 */
		private void setOwnerType(final Class<?> ownerType) {
		
			this.ownerType = ownerType;
		}
		
		/**
		 * @param primaryKey
		 *        the primaryKey to set
		 */
		private void setPrimaryKey(final boolean primaryKey) {
		
			this.primaryKey = primaryKey;
		}
		
		/**
		 * @param typeInJava
		 *        the typeInJava to set
		 */
		private void setTypeInJava(final Class<?> typeInJava) {
		
			this.typeInJava = typeInJava;
		}
		
		/**
		 * @param typeInSQL
		 *        the typeInSQL to set
		 */
		private void setTypeInSQL(final int typeInSQL) {
		
			this.typeInSQL = typeInSQL;
		}
		
		/**
		 * @param uniqueKey
		 *        the uniqueKey to set
		 */
		private void setUniqueKey(final boolean uniqueKey) {
		
			this.uniqueKey = uniqueKey;
		}
		
		@Override
		public int compareTo(MappedColumn other) {
		
			return this.isAutoIncrement() ? -1 : // Se essa coluna for AutoIncremento, então ele deve estar antes, caso contrário
				other.isAutoIncrement() ? 1 : // Se a outra coluna for AutoIncremento, então ele deve estar antes, caso contrário
				this.isPrimaryKey() ? -1 : // Se essa coluna for Chave Primária, então ele deve estar antes, caso contrário
				other.isPrimaryKey() ? 1 : // Se a outra for Chave Primária, então ele deve estar antes, caso contrário
				this.isForeignKey() ? -1 : // Se essa coluna for Chave Estrangeira, então ele deve estar antes, caso contrário
				other.isForeignKey() ? 1 : // Se a outra for Chave Estrangeira, então ele deve estar antes, caso contrário
				this.getColumnName().compareTo(other.getColumnName()); // Segue a ordem Alfabética
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
		
			if(this == obj) {
				return true;
			}
			if(obj == null) {
				return false;
			}
			@SuppressWarnings("unchecked")
			MappedColumn other = (MappedColumn)obj;
			if(columnName == null) {
				if(other.columnName != null) {
					return false;
				}
			}else if( !columnName.equals(other.columnName)) {
				return false;
			}
			return true;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
		
			final int prime = 31;
			int result = 1;
			result = prime * result + ((columnName == null) ? 0 : columnName.hashCode());
			return result;
		}
	}

	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxDao
	 * @package org.jxDao.dao
	 * @date 20/06/2014 - 20:59:32
	 */
	private static enum OPERATIONS {
		ALTER,
		CREATE,
		DELETE,
		DROP,
		INSERT,
		SELECT,
		SELECT_COUNT,
		TRUNCATE,
		UPDATE;
		
		/**
		 * @param value
		 * @return true or false
		 */
		private static boolean valueStartsWithAnyOperation(String value) {
		
			return value.startsWith("ALTER ") || 
				value.startsWith("CREATE ") || 
				value.startsWith("DELETE ") || 
				value.startsWith("DROP ") || 
				value.startsWith("INSERT ") || 
				value.startsWith("SELECT ") || 
				value.startsWith("TRUNCATE ") || 
				value.startsWith("UPDATE ");
		}
	}
}
